<?php

/**
 * 
 * 
 * 
 * @version $Id: Memcache.php 3278 2012-08-31 04:40:13Z locth $
 * @author deathemperor
 *
 */

class Zing_Cache_Memcache implements Zing_Cache_Interface
{
    const COMPRESS_THRESHOLD = 1024; // 1K
    const EXPIRE_DEFAULT = 2592000; // 30 ngay
    private $_local_cache = array();
    private $_key_misses = array();
    private $_servers = '';
    private $_memcache = null;
    private $_profiler = null;
    private $_profiler_enable = false;
    private $_warmup = null;

    //function __construct($server='127.0.0.1',$port='11211', $profiler_enable = false,$warmup = null)
    /**
     * 
     * @param array $servers
     * @param boolean $profiler_enable
     * @param boolean $warmup
     * @param integer $compress_threshold
     * 
     * @return void
     * 
     */
    function __construct($servers = array('host' => '127.0.0.1','port' => '11211'), $profiler_enable = false, $warmup = null, $compress_threshold = null)
    {
        $this->_memcache = new Memcache();
        if (is_array($servers) && count($servers) > 0) {
            for ($i = 0; $i < count($servers); $i ++) {
                $this->_memcache->addServer($servers[$i]['host'], $servers[$i]['port'], true);
            }
        }
        if ($compress_threshold == null || intval($compress_threshold) == 0)
            $this->_memcache->setCompressThreshold(self::COMPRESS_THRESHOLD);
        else
            $this->_memcache->setCompressThreshold($compress_threshold);
        $this->_servers = $servers;
        if ($profiler_enable) {
            $this->_profiler_enable = true;
            $this->_profiler = new Zing_Cache_Helper_Profiler();
        }
        if ($warmup != null) {
            $this->_warmup = $warmup;
        }
    }
    
    /**
     * (non-PHPdoc)
     * @see Zing_Cache_Interface::flushLocalCache()
     */
    public function flushLocalCache()
    {
        foreach ($this->_local_cache as $key => &$value) {
            unset($this->_local_cache[$key]);
        }
        foreach ($this->_key_misses as $key => &$value) {
            unset($this->_key_misses[$key]);
        }
    }

    /**
     * (non-PHPdoc)
     * @see Zing_Cache_Interface::getMultiCache()
     */
    public function getMultiCache($keys)
    {
        if (SGN_Application::$params['refreshcache']) {
            return false;
        }
        //load cache from local cache first
        $local_cache = array();
        $key_miss = array();
        if (is_array($keys) && count($keys) > 0) {
            for ($i = 0; $i < count($keys); $i ++) {
                $key = $keys[$i];
                if (array_key_exists($key, $this->_local_cache)) {
                    $local_cache[$key] = $this->_local_cache[$key];
                } else {
                    $key_miss[] = $key;
                }
            }
        }
        if (! empty($this->_warmup)) {
            $warmup_result = $this->getCacheMultiFromSyncCache($key_miss);
            if (is_array($warmup_result) && count($warmup_result) > 0) {
                $key_miss2 = array();
                for ($i = 0; $i < count($key_miss); $i ++) {
                    $key = $key_miss[$i];
                    if (array_key_exists($key, $warmup_result) && $warmup_result[$key] !== FALSE) {
                        $this->_local_cache[$key] = $warmup_result[$key];
                        $local_cache[$key] = $warmup_result[$key];
                    } else {
                        $key_miss2[] = $key;
                    }
                }
                $key_miss = $key_miss2;
            }
        }
        $cache = $this->_memcache;
        $starttime = gettimeofday(true);
        $result = $cache->get($key_miss);
        $endtime = gettimeofday(true);
        if ($result != null && is_array($result)) {
            $time_average = $endtime - $starttime;
            $time_average = $time_average / count($result);
            $time_average = sprintf("%01.3f", $time_average*1000);
            foreach ($result as $key => &$value) {
                if ($this->_profiler_enable) {
                    $this->_profiler->pushToCacheProfiler($key . " (multikey)", $value, 0, $time_average);
                }
                if (SGN_Application::isRegistered('profiler')) {
                    SGN_Application::get('profiler')->setProfilerData('load',
                    "<a target=\"_blank\" href=\"http://beta.union.zing.vn/sgn/test/viewcache?key=$key&zinstance=" . Zing_Cache_ZingGlobalCache::$properties . "\">$key</a> (multikey)" . " $time_average (" .
                    $this->_calculateCacheSize($value) . ") <<a href='http://union.zing.vn/item/deletecache.html?key=$key'>delete</a>>", 'cache');
                }
                $this->syncache($key, $value);
                $this->_local_cache[$key] = $value;
            }
        } else
            $result = array();
        $result = array_merge($result, $local_cache);
        return $result;
    }

    /**
     * (non-PHPdoc)
     * @see Zing_Cache_Interface::getCache()
     */
    public function getCache($key)
    {
        if (SGN_Application::$params['refreshcache']) {
            return false;
        }
        if ($this->_checkKeyMisses($key))
            return null;
        if (is_array($this->_local_cache) && isset($this->_local_cache[$key])) {
            $cache = $this->_local_cache[$key];
            if ($cache != null)
                return $cache;
        }
        $result = $this->getCacheFromSyncCache($key);
        if ($result !== FALSE) {
            $this->_local_cache[$key] = $result;
            return $result;
        }
        //$cache = Globals::getCache();
        $cache = $this->_memcache;
        $origin_key = $key;
        $starttime = gettimeofday(true);
        $result = $cache->get($key);
        $endtime = gettimeofday(true);
        if (SGN_Application::isRegistered('profiler')) {
            SGN_Application::get('profiler')->setProfilerData('load', 
            "<a target=\"_blank\" href=\"http://beta.union.zing.vn/sgn/test/viewcache?key=$key&zinstance=" . Zing_Cache_ZingGlobalCache::$properties . "\">$key</a>" . " (<i>" . $this->_options['frontendOptions']['cache_id_prefix'] . "</i>) (" .
             $this->_calculateCacheSize($result) . ") <<a href='http://union.zing.vn/item/deletecache.html?key=$key'>delete</a>>", 'cache');
        }
        if ($result === FALSE) {
            $this->_addKeyMisses($origin_key);
        }
        $this->syncache($key, $result);
        if ($this->_profiler_enable)
            $this->_profiler->pushToCacheProfiler($origin_key, $result, $starttime, $endtime);
        $this->_local_cache[$key] = $result;
        return $result;
    }

    /**
     * (non-PHPdoc)
     * @see Zing_Cache_Interface::deleteCache()
     */
    public function deleteCache($key)
    {
        $cache = $this->_memcache;
        if ($cache == null)
            return null;
        $cache->delete($key);
        if (is_array($this->_local_cache) && isset($this->_local_cache[$key])) {
            unset($this->_local_cache[$key]);
        }
        $this->deleteCacheWarmup($key);
    }

    /**
     * (non-PHPdoc)
     * @see Zing_Cache_Interface::setCache()
     */
    public function setCache($key, $value, $expireTime = -1, $compress = 0)
    {
        if (is_null($value))
            return;
        if ($expireTime == - 1)
            $expireTime = self::EXPIRE_DEFAULT;
        $cache = $this->_memcache;
        $this->_removeKeyMisses($key);
        // this help keeping cache live longer, but at a cost of another call
        $this->syncache($key, $value, $expireTime);
        $cache->set($key, $value, $compress, $expireTime);
        if (SGN_Application::isRegistered('profiler')) {
            SGN_Application::get('profiler')->setProfilerData('save', $key . " (" . $this->_calculateCacheSize($value) . ")", 'cache');
        }
        if (is_array($this->_local_cache) && isset($this->_local_cache[$key])) {
            unset($this->_local_cache[$key]);
        }
    }

    /**
     * 
     * @param string $key
     * @param integer $value
     * 
     * @return integer
     */
    public function increment($key, $value = 1)
    {
        $cache = $this->_memcache;
        if (SGN_Application::isRegistered('profiler')) {
            SGN_Application::get('profiler')->setProfilerData('increment', $key . " (" . $this->_calculateCacheSize($value) . ")", 'cache');
        }
        return $cache->increment($key, $value);
    }
    
    /**
     * (non-PHPdoc)
     * @see Zing_Cache_Interface::getProfilerData()
     */
    public function getProfilerData($cache_name = '')
    {
        $output = "";
        if ($this->_profiler_enable) {
            $cache_name .= " " . json_encode($this->_servers);
            $output = $this->_profiler->getProfilerData($cache_name);
        }
        return $output;
    }
    
    public function getCacheServers()
    {
        return $this->_servers;
    }

    //private function ////////
    private function getCacheMultiFromSyncCache($keys = array())
    {
        if (empty($this->_warmup))
            return FALSE;
        $cache = Zing_Cache_ZingGlobalCache::getGlobalCache($this->_warmup);
        if ($cache != null)
            return $cache->getMultiCache($keys);
        else
            return FALSE;
    }

    private function getCacheFromSyncCache($key)
    {
        if (empty($this->_warmup))
            return FALSE;
        $cache = Zing_Cache_ZingGlobalCache::getGlobalCache($this->_warmup);
        if ($cache != null)
            return $cache->getCache($key);
        else
            return FALSE;
    }

    private function deleteCacheWarmup($key)
    {
        if (empty($this->_warmup))
            return;
        $cache = Zing_Cache_ZingGlobalCache::getGlobalCache($this->_warmup);
        if ($cache != null)
            $cache->deleteCache($key);
    }

    private function syncache($key, $value, $expire = -1)
    {
        if (! empty($this->_warmup) && $value !== FALSE && ! empty($key)) {
            $cache = Zing_Cache_ZingGlobalCache::getGlobalCache($this->_warmup);
            if ($cache != null)
                $cache->setCache($key, $value, $expire);
        }
    }

    private function _checkKeyMisses($key)
    {
        if (isset($this->_key_misses[$key]))
            return true;
        else
            return false;
    }

    private function _addKeyMisses($key)
    {
        if (! $this->_checkKeyMisses($key))
            $this->_key_misses[$key] = "1";
    }

    private function _removeKeyMisses($key)
    {
        if ($this->_checkKeyMisses($key)) {
            unset($this->_key_misses[$key]);
        }
    }
    private function _calculateCacheSize($cacheValue)
    {
        $cacheSize = strlen(serialize($cacheValue));
        $color = 'green';
        if ($cacheSize > 1024) {
            $color = 'red';
        }
        $cacheSize = SGN_Application::bytesFormat($cacheSize);
        return "<span style='color:$color'>" . $cacheSize . "</span>";
    }
}
?>
